# -*- coding: utf-8 -*-
# @Time: 2021/11/5 14:49
# @Author: lijinxi
# @File    : service_placement.py
# @desc

import numpy as np
from gurobipy import GRB
import gurobipy as gp
import random
import time


class ServicePlacement(object):
    def __init__(self, number_access_points=25, number_edge_servers=25, number_requests=200,
                 type_network_functions=10):
        self.number_access_points = number_access_points  # 接入点数量
        self.number_edge_servers = number_edge_servers  # 边缘服务器数量
        self.number_requests = number_requests  # 请求数量
        self.type_network_functions = type_network_functions  # 网络功能种类数

        # 系统设置
        self.computing_capacity_vector = None  # 每种网络功能需要的计算容量
        self.node_availability_vector = None  # 每个边缘服务器的可用性
        self.function_availability_vector = None
        self.delay_between_nodes = 100  # 100us
        self.node_computing_capacity = None  # 节点计算容量
        self.node_communication_capacity = None  # 节点通信容量

        # 用户请求
        self.bandwidth_capacity_vector = None  # 服务放置中不考虑
        self.tolerance_delay_vector = None  # 最大时延
        self.availability_vector = None  # 可用性
        self.ingress_egress_vector = None  # 出入口
        self.process_delay_vector = None  # 节点上的处理时延
        self.network_function_matrix = None  # 指定的网络功能

        # 决策变量
        self.admitted_requests = None
        self.primary_placement = None
        self.backup_placement = None

        self.model = gp.Model("service placement")  # gurobi模型
        self.model.setParam(GRB.Param.TimeLimit, 16)

    # 初始化决策变量
    def initialize_decision_variables(self):
        self.admitted_requests = self.model.addMVar(shape=(self.number_requests,), vtype=GRB.BINARY,
                                                    name="admitted requests")
        self.primary_placement = self.model.addMVar(
            shape=(self.number_requests, self.type_network_functions, self.number_edge_servers), vtype=GRB.BINARY,
            name="primary placement")  # 二维转三维, (请求*网络功能数, 边缘服务器数量)
        self.backup_placement = self.model.addMVar(
            shape=(self.number_requests, self.type_network_functions, self.number_edge_servers), vtype=GRB.BINARY,
            name="backup placement")
        print(
            f"Decision Variables:\n admitted requests: {self.admitted_requests}\n"
            f"primary placement: {self.primary_placement}\nbackup placement:{self.backup_placement}", )

    # 初始化用户的请求,后续只更改接入位置,而不更改其他参数
    def initialize_requests(self, computing_capacity_range=None, bandwidth_capacity_range=None,
                            process_delay_range=None, node_availability_range=None, function_availability_range=None,
                            availability_range=None, tolerance_delay_range=None, ratio=1.0):
        if bandwidth_capacity_range is None:
            bandwidth_capacity_range = [30, 50 + 1]
        if computing_capacity_range is None:
            computing_capacity_range = [50, 100 + 1]
        if process_delay_range is None:
            process_delay_range = [50, 150 + 1]  # us
        if node_availability_range is None:
            node_availability_range = [9999, 9999 + 1]  # [0.999,0.9999]
        if function_availability_range is None:
            function_availability_range = [9990, 9999 + 1]  # [0.99,0.999]
        if availability_range is None:
            availability_range = [0.99, 0.999, 0.9999, 0.99999]
        if tolerance_delay_range is None:
            tolerance_delay_range = [20 * 1000, 150 * 1000 + 1]  # [20,150]ms
        np.random.seed(100)  # 保证一致性
        # 设置每个请求的带宽需要
        self.bandwidth_capacity_vector = np.random.randint(*bandwidth_capacity_range, size=(self.number_requests,),
                                                           dtype=np.int8)
        # 设置每种网络功能的计算能力需要
        print(f"bandwidth_capacity_vector:{self.bandwidth_capacity_vector}")
        self.computing_capacity_vector = np.random.randint(*computing_capacity_range,
                                                           size=(self.type_network_functions,),
                                                           dtype=np.int8)
        print(f"computing_capacity_vector:{self.computing_capacity_vector}")
        # 设置物理节点可用性
        self.node_availability_vector = np.random.randint(*node_availability_range, size=(self.number_edge_servers,),
                                                          dtype=np.int64) / 10000.0
        print(f"node_availability_vector:{self.node_availability_vector}")
        # VNF的可用性
        self.function_availability_vector = np.random.randint(*function_availability_range,
                                                              size=(self.type_network_functions,),
                                                              dtype=np.int64) / 10000.0
        print(f"function_availability_vector:{self.function_availability_vector}")
        # 单个VNF处理时延
        self.process_delay_vector = np.random.randint(*process_delay_range, size=(self.type_network_functions,),
                                                      dtype=np.int32)
        print(f"process_delay_vector:{self.process_delay_vector}")
        # TODO:时延容忍, VNF越多时间越长
        self.tolerance_delay_vector = np.random.randint(*tolerance_delay_range, size=(self.number_requests,),
                                                        dtype=np.int64)
        # print(f"tolerance_delay_vector:{self.tolerance_delay_vector}")
        # 生成每个请求的网络功能
        self.network_function_matrix = np.zeros(shape=(self.number_requests, self.type_network_functions),
                                                dtype=np.int8)
        # 设置请求可用性, 链长的可用性较低
        random.seed(100)
        self.availability_vector = np.zeros(shape=(self.number_requests,), dtype=np.float64)
        number_functions = np.random.randint(2, 5 + 1, size=(self.number_requests,), dtype=np.int8)  # 2-5个VNF
        index = [i for i in range(0, self.type_network_functions)]
        for i, number in enumerate(number_functions):
            if number >= 6:
                self.availability_vector[i] = availability_range[0]  # TODO：计算方式
            else:
                self.availability_vector[i] = random.choice(availability_range)  # 随机设置一个
                # self.availability_vector[i] = 0.99999
            for j in random.sample(index, number):
                self.network_function_matrix[i][j] = 1
        # print(f"availability vector:{self.availability_vector}")
        # 根据请求的要求, 设置计算容量
        computing_capacity = ratio * np.sum(
            self.network_function_matrix * self.computing_capacity_vector) // self.number_edge_servers + 10
        self.node_computing_capacity = np.zeros(shape=(self.number_edge_servers,), dtype=np.int64)
        for i in range(self.number_edge_servers):
            self.node_computing_capacity[i] = computing_capacity  # 容量都一样
        # 设置通信容量
        communication_capacity = 2 * np.sum(self.bandwidth_capacity_vector) // self.number_edge_servers + 10
        self.node_communication_capacity = np.zeros(shape=(self.number_access_points,), dtype=np.int64)
        for i in range(self.number_access_points):
            self.node_communication_capacity[i] = communication_capacity
        print(f"computing_capacity:{computing_capacity},communication_capacity:{communication_capacity}")

    def primary_placement_stage(self, alpha=0.60):
        self.model.setObjective(self.admitted_requests.sum(), GRB.MAXIMIZE)  # 目标值
        # 被允许的请求，所有VNF被放置
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                # 每个请求的VNF被放在一个节点
                self.model.addConstr(self.primary_placement[i, j, :].sum() == self.admitted_requests[i] *
                                     self.network_function_matrix[i][j], f"admitted -vnf {i}-{j}")
            # 不重复
            for k in range(self.number_access_points):
                self.model.addConstr(self.primary_placement[i, :, k].sum() <= 1, f"not all {i}-{k}")
        # 计算容量限制
        for k in range(self.number_edge_servers):
            _value = 0
            for i in range(self.number_requests):
                _value += self.primary_placement[i, :, k] @ self.computing_capacity_vector
            self.model.addConstr(_value <= self.node_computing_capacity[k] * alpha, f"less than {k}")
        # TODO：时间约束
        self.model.optimize()

    def backup_placement_stage(self, beta=0.40):
        primary_fixed = np.array(self.primary_placement.X)
        admitted_value = np.array(self.admitted_requests.X)  # 取出值
        self.model.setObjective(self.admitted_requests @ admitted_value, GRB.MAXIMIZE)  # 目标值
        # 特定的备份放置被允许
        for i in range(self.number_requests):
            for j in range(self.type_network_functions):
                # 每个请求的VNF被放在一个节点
                self.model.addConstr(self.backup_placement[i, j, :].sum() <= self.admitted_requests[i] *
                                     self.network_function_matrix[i][j], f"admitted -vnf -backup {i}-{j}")

        # TODO:主VNF和备份VNF放在不同的节点上
        # for i in range(self.number_requests):
        #     if admitted_value[i] == 1:
        #         for j in range(self.type_network_functions):
        #             self.model.addConstr(primary_fixed[i][j] @ self.backup_placement[i, j, :] == 0,
        #                                  f"different {i}-{j}")
        # # 计算容量限制
        for k in range(self.number_edge_servers):
            _value = 0
            for i in range(self.number_requests):
                _value += self.backup_placement[i, :, k] @ self.computing_capacity_vector
            self.model.addConstr(_value <= self.node_computing_capacity[k] * beta, f"less than {k} -backup")

        # 可获得性计算
        for i in range(self.number_requests):
            if admitted_value[i] == 1:  # 在上一步被放置
                value = 0
                for j in range(self.type_network_functions):
                    if self.network_function_matrix[i][j] == 1:
                        for k in range(self.number_edge_servers):
                            if primary_fixed[i, j, k] == 1:
                                value += (1 - (1 - primary_fixed[i, j, k] * self.node_availability_vector[k] *
                                               self.function_availability_vector[j]) * (
                                                  1 - self.backup_placement[i, j, k] *
                                                  self.node_availability_vector[k] *
                                                  self.function_availability_vector[j]))
                self.model.addConstr(
                    value >= self.admitted_requests[i] * np.power(self.availability_vector[i],
                                                                  1.0 / self.network_function_matrix[i].sum()) *
                    self.network_function_matrix[i].sum(), "availability than {i}")
        self.model.optimize()
        print(self.backup_placement.X.sum())


def method_by_computation_ratio():
    x = []
    y = []
    for i in range(0, 101, 5):
        sp = ServicePlacement()
        sp.initialize_decision_variables()
        sp.initialize_requests()
        sp.primary_placement_stage(i / 100)
        print("=" * 50)
        sp.backup_placement_stage(1 - i / 100)
        x.append(i / 100)
        y.append(sp.model.objVal)
    print(x, y)


def method_by_computation():
    x = []
    y = []
    for i in range(0, 201, 10):
        sp = ServicePlacement()
        sp.initialize_decision_variables()
        sp.initialize_requests(ratio=i / 100)
        sp.primary_placement_stage(0.6)
        print("=" * 50)
        sp.backup_placement_stage(0.4)
        x.append(i / 100)
        y.append(sp.model.objVal)
    print(x, y)


if __name__ == '__main__':
    sp = ServicePlacement(number_requests=1600)
    sp.initialize_decision_variables()
    sp.initialize_requests()
    t = time.time()
    sp.primary_placement_stage()
    print("=" * 50)
    sp.backup_placement_stage()
    print(time.time() - t)
